home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / sourceBox.js < prev    next >
Text File  |  2010-01-15  |  27KB  |  766 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5.  
  6.  
  7. /* Defines the API for SourceBoxDecorator and provides the default implementation.
  8.  * Decorators are passed the source box on construction, called to create the HTML,
  9.  * and called whenever the user scrolls the view.
  10.  */
  11. Firebug.SourceBoxDecorator = function(sourceBox){}
  12.  
  13. Firebug.SourceBoxDecorator.sourceBoxCounter = 0;
  14.  
  15. Firebug.SourceBoxDecorator.prototype =
  16. {
  17.     onSourceBoxCreation: function(sourceBox)
  18.     {
  19.         // allow panel-document unique ids to be generated for lines.
  20.         sourceBox.uniqueId = ++Firebug.SourceBoxDecorator.sourceBoxCounter;
  21.     },
  22.     /* called on a delay after the view port is updated, eg vertical scroll
  23.      * The sourceBox will contain lines from firstRenderedLine to lastRenderedLine
  24.      * The user will be able to see sourceBox.firstViewableLine to sourceBox.lastViewableLine
  25.      */
  26.     decorate: function(sourceBox, sourceFile)
  27.     {
  28.         return;
  29.     },
  30.  
  31.     /* called once as each line is being rendered.
  32.     * @param lineNo integer 1-maxLineNumbers
  33.     */
  34.     getUserVisibleLineNumber: function(sourceBox, lineNo)
  35.     {
  36.         return lineNo;
  37.     },
  38.  
  39.     /* call once as each line is being rendered.
  40.     * @param lineNo integer 1-maxLineNumbers
  41.     */
  42.     getLineHTML: function(sourceBox, lineNo)
  43.     {
  44.         var html = escapeForSourceLine(sourceBox.lines[lineNo-1]);
  45.  
  46.         // If the pref says so, replace tabs by corresponding number of spaces.
  47.         if (Firebug.replaceTabs > 0)
  48.         {
  49.             var space = new Array(Firebug.replaceTabs + 1).join(" ");
  50.             html = html.replace(/\t/g, space);
  51.         }
  52.  
  53.         return html;
  54.     },
  55.  
  56.     /*
  57.      * @return a string unique to the sourcebox and line number, valid in getElementById()
  58.      */
  59.     getLineId: function(sourceBox, lineNo)
  60.     {
  61.         return 'sb' + sourceBox.uniqueId + '-L' + lineNo;
  62.     },
  63. }
  64.  
  65.  
  66. /*
  67.  * @panel Firebug.SourceBoxPanel: Intermediate level class for showing lines of source, eg Script Panel
  68.  * Implements a 'viewport' to render only the lines the user is viewing or has recently viewed.
  69.  * Scroll events or scrollToLine calls are converted to viewableRange line number range.
  70.  * The range of lines is rendered, skipping any that have already been rendered. Then if the
  71.  * new line range overlaps the old line range, done; else delete the old range.
  72.  * That way the lines kept contiguous.
  73.  * The rendering details are delegated to SourceBoxDecorator; each source line may be expanded into
  74.  * more rendered lines.
  75.  */
  76.  
  77. Firebug.SourceBoxPanel = function() {};
  78. /* @lends */
  79. Firebug.SourceBoxPanel = extend( extend(Firebug.MeasureBox, Firebug.ActivablePanel),
  80. {
  81.  
  82.     initialize: function(context, doc)
  83.     {
  84.         this.onResize =  bind(this.resizer, this);
  85.  
  86.         this.sourceBoxes = {};
  87.         this.decorator = this.getDecorator();
  88.         Firebug.Panel.initialize.apply(this, arguments);
  89.     },
  90.  
  91.     initializeNode: function(panelNode)
  92.     {
  93.         this.resizeEventTarget = Firebug.chrome.$('fbContentBox');
  94.         this.resizeEventTarget.addEventListener("resize", this.onResize, true);
  95.     },
  96.  
  97.     reattach: function(doc)
  98.     {
  99.         var oldEventTarget = this.resizeEventTarget;
  100.         oldEventTarget.removeEventListener("resize", this.onResize, true);
  101.         Firebug.Panel.reattach.apply(this, arguments);
  102.         this.resizeEventTarget = Firebug.chrome.$('fbContentBox');
  103.         this.resizeEventTarget.addEventListener("resize", this.onResize, true);
  104.     },
  105.  
  106.     destroyNode: function()
  107.     {
  108.         Firebug.Panel.destroyNode.apply(this, arguments);
  109.         this.resizeEventTarget.removeEventListener("resize", this.onResize, true);
  110.     },
  111.  
  112.     // **************************************
  113.     /*  Panel extension point.
  114.      *  Called just before box is shown
  115.      */
  116.     updateSourceBox: function(sourceBox)
  117.     {
  118.  
  119.     },
  120.  
  121.     /* Panel extension point. Called on panel initialization
  122.      * @return Must implement SourceBoxDecorator API.
  123.      */
  124.     getDecorator: function()
  125.     {
  126.         return new Firebug.SourceBoxDecorator();
  127.      },
  128.  
  129.      /* Panel extension point
  130.       * @return string eg "js" or "css"
  131.       */
  132.     getSourceType: function()
  133.     {
  134.         throw "Need to override in extender";
  135.     },
  136.  
  137.     // **************************************
  138.     disablePanel: function(module)
  139.     {
  140.         this.sourceBoxes = {};  // clear so we start fresh if enabled
  141.         Firebug.ActivablePanel.disablePanel.apply(this, arguments);
  142.     },
  143.  
  144.     getSourceLinesFrom: function(selection)
  145.     {
  146.         // https://developer.mozilla.org/en/DOM/Selection
  147.         if (selection.isCollapsed)
  148.             return "";
  149.  
  150.         var anchorSourceRow = getAncestorByClass(selection.anchorNode, "sourceRow");
  151.         var focusSourceRow = getAncestorByClass(selection.focusNode, "sourceRow");
  152.         if (anchorSourceRow == focusSourceRow)
  153.         {
  154.             return selection.toString();// trivial case
  155.         }
  156.         var buf = this.getSourceLine(anchorSourceRow, selection.anchorOffset);
  157.  
  158.         var currentSourceRow = anchorSourceRow.nextSibling;
  159.         while(currentSourceRow && (currentSourceRow != focusSourceRow) && hasClass(currentSourceRow, "sourceRow"))
  160.         {
  161.             buf += this.getSourceLine(currentSourceRow);
  162.             currentSourceRow = currentSourceRow.nextSibling;
  163.         }
  164.         buf += this.getSourceLine(focusSourceRow, 0, selection.focusOffset);
  165.         return buf;
  166.     },
  167.  
  168.     getSourceLine: function(sourceRow, beginOffset, endOffset)
  169.     {
  170.         var source = getChildByClass(sourceRow, "sourceRowText").textContent;
  171.         if (endOffset)
  172.             source = source.substring(beginOffset, endOffset);
  173.         else if (beginOffset)
  174.             source = source.substring(beginOffset);
  175.         else
  176.             source = source;
  177.  
  178.         return source;
  179.     },
  180.  
  181.     // ****************************************************************************************
  182.  
  183.     getSourceBoxBySourceFile: function(sourceFile)
  184.     {
  185.         if (sourceFile.href)
  186.         {
  187.             var sourceBox = this.getSourceBoxByURL(sourceFile.href);
  188.             if (sourceBox && sourceBox.repObject == sourceFile)
  189.                 return sourceBox;
  190.             else
  191.                 return null;  // cause a new one to be created
  192.         }
  193.     },
  194.  
  195.     getSourceBoxByURL: function(url)
  196.     {
  197.         return url ? this.sourceBoxes[url] : null;
  198.     },
  199.  
  200.     renameSourceBox: function(oldURL, newURL)
  201.     {
  202.         var sourceBox = this.sourceBoxes[oldURL];
  203.         if (sourceBox)
  204.         {
  205.             delete this.sourceBoxes[oldURL];
  206.             this.sourceBoxes[newURL] = sourceBox;
  207.         }
  208.     },
  209.  
  210.     showSourceFile: function(sourceFile)
  211.     {
  212.         var sourceBox = this.getSourceBoxBySourceFile(sourceFile);
  213.         if (!sourceBox)
  214.             sourceBox = this.createSourceBox(sourceFile);
  215.  
  216.         this.showSourceBox(sourceBox);
  217.     },
  218.  
  219.     showSourceBox: function(sourceBox)
  220.     {
  221.         if (this.selectedSourceBox)
  222.             collapse(this.selectedSourceBox, true);
  223.  
  224.         this.selectedSourceBox = sourceBox;
  225.         delete this.currentSearch;
  226.  
  227.         if (sourceBox)
  228.         {
  229.             this.reView(sourceBox);
  230.             this.updateSourceBox(sourceBox);
  231.             collapse(sourceBox, false);
  232.         }
  233.     },
  234.  
  235.     /* Private, do not call outside of this object
  236.     * A sourceBox is a div with additional operations and state.
  237.     * @param sourceFile there is at most one sourceBox for each sourceFile
  238.     */
  239.     createSourceBox: function(sourceFile)  // decorator(sourceFile, sourceBox)
  240.     {
  241.         var sourceBox = this.initializeSourceBox(sourceFile);
  242.  
  243.         sourceBox.decorator = this.decorator;
  244.  
  245.         // Framework connection
  246.         sourceBox.decorator.onSourceBoxCreation(sourceBox);
  247.  
  248.         this.sourceBoxes[sourceFile.href] = sourceBox;
  249.  
  250.         this.panelNode.appendChild(sourceBox);
  251.         this.setSourceBoxLineSizes(sourceBox);
  252.  
  253.         return sourceBox;
  254.     },
  255.  
  256.     initializeSourceBox: function(sourceFile)
  257.     {
  258.         var sourceBox = this.document.createElement("div");
  259.         setClass(sourceBox, "sourceBox");
  260.         collapse(sourceBox, true);
  261.  
  262.         var lines = sourceFile.loadScriptLines(this.context);
  263.         if (!lines)
  264.         {
  265.             lines = ["Failed to load source for sourceFile "+sourceFile];
  266.         }
  267.  
  268.         sourceBox.lines = lines;
  269.         sourceBox.repObject = sourceFile;
  270.  
  271.         sourceBox.maximumLineNumber = lines.length;
  272.         sourceBox.maxLineNoChars = (sourceBox.maximumLineNumber + "").length;
  273.  
  274.         sourceBox.getLineNode =  function(lineNo)
  275.         {
  276.             // XXXjjb this method is supposed to return null if the lineNo is not in the viewport
  277.             return $(this.decorator.getLineId(this, lineNo), this.ownerDocument);
  278.         };
  279.  
  280.         var paddedSource =
  281.             "<div class='topSourcePadding'>" +
  282.                 "<div class='sourceRow'><div class='sourceLine'></div><div class='sourceRowText'></div></div>"+
  283.             "</div>"+
  284.             "<div class='sourceViewport'></div>"+
  285.             "<div class='bottomSourcePadding'>"+
  286.                 "<div class='sourceRow'><div class='sourceLine'></div><div class='sourceRowText'></div></div>"+
  287.             "</div>";
  288.  
  289.         appendInnerHTML(sourceBox, paddedSource);
  290.  
  291.         sourceBox.viewport = getChildByClass(sourceBox, 'sourceViewport');
  292.         return sourceBox;
  293.     },
  294.  
  295.     setSourceBoxLineSizes: function(sourceBox)
  296.     {
  297.         var view = sourceBox.viewport;
  298.  
  299.         var lineNoCharsSpacer = "";
  300.         for (var i = 0; i < sourceBox.maxLineNoChars; i++)
  301.               lineNoCharsSpacer += "0";
  302.  
  303.         this.startMeasuring(view);
  304.         var size = this.measureText(lineNoCharsSpacer);
  305.         this.stopMeasuring();
  306.  
  307.         sourceBox.lineHeight = size.height + 1;
  308.         sourceBox.lineNoWidth = size.width;
  309.  
  310.         var view = sourceBox.viewport; // TODO some cleaner way
  311.         view.previousSibling.firstChild.firstChild.style.width = sourceBox.lineNoWidth + "px";
  312.         view.nextSibling.firstChild.firstChild.style.width = sourceBox.lineNoWidth +"px";
  313.  
  314.     },
  315.  
  316.     /*
  317.      * @return SourceLink to currently selected source file
  318.      */
  319.     getSourceLink: function(lineNo)
  320.     {
  321.         if (!this.selectedSourceBox)
  322.             return;
  323.         if (!lineNo)
  324.             lineNo = this.getCentralLine(this.selectedSourceBox);
  325.         return new SourceLink(this.selectedSourceBox.repObject.href, lineNo, this.getSourceType());
  326.     },
  327.  
  328.     /* Select sourcebox with href, scroll lineNo into center, highlight lineNo with highlighter given
  329.      * @param href a URL, null means the selected sourcefile
  330.      * @param lineNo integer 1-maximumLineNumber
  331.      * @param highlighter callback, a function(sourceBox). sourceBox.centralLine will be lineNo
  332.      */
  333.     scrollToLine: function(href, lineNo, highlighter)
  334.     {
  335.         if (this.context.scrollTimeout)
  336.         {
  337.             this.context.clearTimeout(this.contextscrollTimeout);
  338.             delete this.context.scrollTimeout
  339.         }
  340.  
  341.         if (href)
  342.         {
  343.             if (!this.selectedSourceBox || this.selectedSourceBox.repObject.href != href)
  344.             {
  345.                 var sourceFile = this.context.sourceFileMap[href];
  346.                 if (!sourceFile)
  347.                 {
  348.                     return;
  349.                 }
  350.                 this.navigate(sourceFile);
  351.             }
  352.         }
  353.  
  354.         this.context.scrollTimeout = this.context.setTimeout(bindFixed(function()
  355.         {
  356.             if (!this.selectedSourceBox)
  357.             {
  358.                 return;
  359.             }
  360.  
  361.             this.selectedSourceBox.targetedLine = lineNo;
  362.  
  363.             // At this time we know which sourcebox is selected but the viewport is not selected.
  364.             // We need to scroll, let the scroll handler set the viewport, then highlight any lines visible.
  365.             var skipScrolling = false;
  366.             if (this.selectedSourceBox.firstViewableLine && this.selectedSourceBox.lastViewableLine)
  367.             {
  368.                 var linesFromTop = lineNo - this.selectedSourceBox.firstViewableLine;
  369.                 var linesFromBot = this.selectedSourceBox.lastViewableLine - lineNo;
  370.                 skipScrolling = (linesFromTop > 3 && linesFromBot > 3);
  371.             }
  372.             else  // the selectedSourceBox has not been built
  373.             {
  374.             }
  375.  
  376.             if (highlighter)
  377.                  this.selectedSourceBox.highlighter = highlighter;
  378.  
  379.             if (!skipScrolling)
  380.             {
  381.                 var viewRange = this.getViewRangeFromTargetLine(this.selectedSourceBox, lineNo);
  382.                 this.selectedSourceBox.newScrollTop = this.getScrollTopFromViewRange(this.selectedSourceBox, viewRange);
  383.                 this.selectedSourceBox.scrollTop = this.selectedSourceBox.newScrollTop; // *may* cause scrolling
  384.             }
  385.  
  386.             if (this.selectedSourceBox.highlighter)
  387.                 this.applyDecorator(this.selectedSourceBox); // may need to highlight even if we don't scroll
  388.  
  389.         }, this));
  390.     },
  391.  
  392.     /*
  393.      * @return a highlighter function(sourceBox) that puts a class on the line for a time slice
  394.      */
  395.     jumpHighlightFactory: function(lineNo, context)
  396.     {
  397.         return function jumpHighlightIfInView(sourceBox)
  398.         {
  399.             var  lineNode = sourceBox.getLineNode(lineNo);
  400.             if (lineNode)
  401.             {
  402.                 setClassTimed(lineNode, "jumpHighlight", context);
  403.             }
  404.             else
  405.             {
  406.             }
  407.  
  408.             return false; // not sticky
  409.         }
  410.     },
  411.  
  412.     /*
  413.      * resize and scroll event handler
  414.      */
  415.     resizer: function(event)
  416.     {
  417.         // The resize target is Firebug as a whole. But most of the UI needs no special code for resize.
  418.         // But our SourceBoxPanel has viewport that will change size.
  419.         if (this.selectedSourceBox && this.visible)
  420.         {
  421.             this.reView(this.selectedSourceBox);
  422.         }
  423.     },
  424.  
  425.     reView: function(sourceBox, clearCache)  // called for all scroll events, including any time sourcebox.scrollTop is set
  426.     {
  427.         if (sourceBox.targetedLine)
  428.         {
  429.             sourceBox.targetLineNumber = sourceBox.targetedLine;
  430.             var viewRange = this.getViewRangeFromTargetLine(sourceBox, sourceBox.targetedLine);
  431.             delete sourceBox.targetedLine;
  432.         }
  433.         else
  434.         {
  435.             var viewRange = this.getViewRangeFromScrollTop(sourceBox, sourceBox.scrollTop);
  436.         }
  437.  
  438.         if (clearCache)
  439.         {
  440.             this.clearSourceBox(sourceBox);
  441.         }
  442.         else if (sourceBox.scrollTop === sourceBox.lastScrollTop && sourceBox.clientHeight === sourceBox.lastClientHeight)
  443.         {
  444.             if (sourceBox.firstRenderedLine <= viewRange.firstLine && sourceBox.lastRenderedLine >= viewRange.lastLine)
  445.             {
  446.                 return;
  447.             }
  448.         }
  449.  
  450.         dispatch([Firebug.A11yModel], "onBeforeViewportChange", [this]);  // XXXjjb TODO where should this be?
  451.         this.buildViewAround(sourceBox, viewRange);
  452.  
  453.         if (Firebug.uiListeners.length > 0)
  454.         {
  455.             var link = new SourceLink(sourceBox.repObject.href, sourceBox.centralLine, this.getSourceType());
  456.             dispatch(Firebug.uiListeners, "onViewportChange", [link]);
  457.         }
  458.  
  459.         sourceBox.lastScrollTop = sourceBox.scrollTop;
  460.         sourceBox.lastClientHeight = sourceBox.clientHeight;
  461.     },
  462.  
  463.     buildViewAround: function(sourceBox, viewRange)
  464.     {
  465.         try
  466.         {
  467.             this.updateViewportCache(sourceBox, viewRange);
  468.         }
  469.         catch(exc)
  470.         {
  471.         }
  472.  
  473.         this.setViewportPadding(sourceBox, viewRange);
  474.  
  475.         sourceBox.centralLine = Math.floor( (viewRange.lastLine - viewRange.firstLine)/2 );
  476.  
  477.         this.applyDecorator(sourceBox);
  478.  
  479.         return;
  480.     },
  481.  
  482.     updateViewportCache: function(sourceBox, viewRange)
  483.     {
  484.         var cacheHit = this.insertedLinesOverlapCache(sourceBox, viewRange);
  485.  
  486.         if (!cacheHit)
  487.         {
  488.             this.clearSourceBox(sourceBox);  // no overlap, remove old range
  489.             sourceBox.firstRenderedLine = viewRange.firstLine; // reset cached range
  490.             sourceBox.lastRenderedLine = viewRange.lastLine;
  491.         }
  492.         else  // cache overlap, expand range of cache
  493.         {
  494.             sourceBox.firstRenderedLine = Math.min(viewRange.firstLine, sourceBox.firstRenderedLine);
  495.             sourceBox.lastRenderedLine = Math.max(viewRange.lastLine, sourceBox.lastRenderedLine);
  496.         }
  497.         sourceBox.firstViewableLine = viewRange.firstLine;  // todo actually check that these are viewable
  498.         sourceBox.lastViewableLine = viewRange.lastLine;
  499.         sourceBox.numberOfRenderedLines = sourceBox.lastRenderedLine - sourceBox.firstRenderedLine + 1;
  500.  
  501.     },
  502.  
  503.     /*
  504.      * Add lines from viewRange, but do not adjust first/lastRenderedLine.
  505.      * @return true if viewRange overlaps first/lastRenderedLine
  506.      */
  507.     insertedLinesOverlapCache: function(sourceBox, viewRange)
  508.     {
  509.         var topCacheLine = null;
  510.         var cacheHit = false;
  511.         for (var line = viewRange.firstLine; line <= viewRange.lastLine; line++)
  512.         {
  513.             if (line >= sourceBox.firstRenderedLine && line <= sourceBox.lastRenderedLine )
  514.             {
  515.                 cacheHit = true;
  516.                 continue;
  517.             }
  518.  
  519.             var lineHTML = this.getSourceLineHTML(sourceBox, line);
  520.  
  521.             var ref = null;
  522.             if (line < sourceBox.firstRenderedLine)   // prepend if we are above the cache
  523.             {
  524.                 if (!topCacheLine)
  525.                     topCacheLine = sourceBox.getLineNode(sourceBox.firstRenderedLine);
  526.                 ref = topCacheLine;
  527.             }
  528.  
  529.             var newElement = appendInnerHTML(sourceBox.viewport, lineHTML, ref);
  530.         }
  531.         return cacheHit;
  532.     },
  533.  
  534.     clearSourceBox: function(sourceBox)
  535.     {
  536.         if (sourceBox.firstRenderedLine)
  537.         {
  538.             var topMostCachedElement = sourceBox.getLineNode(sourceBox.firstRenderedLine);  // eg 1
  539.             var totalCached = sourceBox.lastRenderedLine - sourceBox.firstRenderedLine + 1;   // eg 20 - 1 + 1 = 19
  540.             if (topMostCachedElement && totalCached)
  541.                 this.removeLines(sourceBox, topMostCachedElement, totalCached);
  542.         }
  543.         sourceBox.lastRenderedLine = 0;
  544.         sourceBox.firstRenderedLine = 0;
  545.         sourceBox.numberOfRenderedLines = 0;
  546.     },
  547.  
  548.     getSourceLineHTML: function(sourceBox, i)
  549.     {
  550.         var lineNo = sourceBox.decorator.getUserVisibleLineNumber(sourceBox, i);
  551.         var lineHTML = sourceBox.decorator.getLineHTML(sourceBox, i);
  552.         var lineId = sourceBox.decorator.getLineId(sourceBox, i);    // decorator lines may not have ids
  553.  
  554.         var lineNoText = this.getTextForLineNo(lineNo, sourceBox.maxLineNoChars);
  555.  
  556.         var theHTML =
  557.             '<div '
  558.                + (lineId ? ('id="' + lineId + '"') : "")
  559.                + ' class="sourceRow" role="presentation"><a class="'
  560.                +  'sourceLine' + '" role="presentation">'
  561.                + lineNoText
  562.                + '</a><span class="sourceRowText" role="presentation">'
  563.                + lineHTML
  564.                + '</span></div>';
  565.  
  566.         return theHTML;
  567.     },
  568.  
  569.     getTextForLineNo: function(lineNo, maxLineNoChars)
  570.     {
  571.         // Make sure all line numbers are the same width (with a fixed-width font)
  572.         var lineNoText = lineNo + "";
  573.         while (lineNoText.length < maxLineNoChars)
  574.             lineNoText = " " + lineNoText;
  575.  
  576.         return lineNoText;
  577.     },
  578.  
  579.     removeLines: function(sourceBox, firstRemoval, totalRemovals)
  580.     {
  581.         for(var i = 1; i <= totalRemovals; i++)
  582.         {
  583.             var nextSourceLine = firstRemoval;
  584.             firstRemoval = firstRemoval.nextSibling;
  585.             sourceBox.viewport.removeChild(nextSourceLine);
  586.         }
  587.     },
  588.  
  589.     getCentralLine: function(sourceBox)
  590.     {
  591.         return sourceBox.centralLine;
  592.     },
  593.  
  594.     getViewRangeFromTargetLine: function(sourceBox, targetLineNumber)
  595.     {
  596.         var viewRange = {firstLine: 1, centralLine: targetLineNumber, lastLine: 1};
  597.  
  598.         var averageLineHeight = this.getAverageLineHeight(sourceBox);
  599.         var panelHeight = this.panelNode.clientHeight;
  600.         var linesPerViewport = Math.round((panelHeight / averageLineHeight) + 1);
  601.  
  602.         viewRange.firstLine = Math.round(targetLineNumber - linesPerViewport / 2);
  603.  
  604.         if (viewRange.firstLine <= 0)
  605.             viewRange.firstLine = 1;
  606.  
  607.         viewRange.lastLine = viewRange.firstLine + linesPerViewport;
  608.  
  609.         if (viewRange.lastLine > sourceBox.maximumLineNumber)
  610.             viewRange.lastLine = sourceBox.maximumLineNumber;
  611.  
  612.         return viewRange;
  613.     },
  614.  
  615.     /*
  616.      * Use the average height of source lines in the cache to estimate where the scroll bar points based on scrollTop
  617.      */
  618.     getViewRangeFromScrollTop: function(sourceBox, scrollTop)
  619.     {
  620.         var viewRange = {};
  621.         var averageLineHeight = this.getAverageLineHeight(sourceBox);
  622.         viewRange.firstLine = Math.floor(scrollTop / averageLineHeight + 1);
  623.  
  624.         var panelHeight = this.panelNode.clientHeight;
  625.         var viewableLines = Math.ceil((panelHeight / averageLineHeight) + 1);
  626.         viewRange.lastLine = viewRange.firstLine + viewableLines;
  627.         if (viewRange.lastLine > sourceBox.maximumLineNumber)
  628.             viewRange.lastLine = sourceBox.maximumLineNumber;
  629.  
  630.         viewRange.centralLine = Math.floor((viewRange.lastLine - viewRange.firstLine)/2);
  631.  
  632.         return viewRange;
  633.     },
  634.  
  635.     /*
  636.      * inverse of the getViewRangeFromScrollTop.
  637.      * If the viewRange was set by targetLineNumber, then this value become the new scroll top
  638.      *    else the value will be the same as the scrollbar's given value of scrollTop.
  639.      */
  640.     getScrollTopFromViewRange: function(sourceBox, viewRange)
  641.     {
  642.         var averageLineHeight = this.getAverageLineHeight(sourceBox);
  643.         var scrollTop = Math.floor(averageLineHeight * (viewRange.firstLine - 1));
  644.  
  645.         return scrollTop;
  646.     },
  647.  
  648.     /*
  649.      * The virtual sourceBox height is the averageLineHeight * max lines
  650.      * @return float
  651.      */
  652.     getAverageLineHeight: function(sourceBox)
  653.     {
  654.         var averageLineHeight = sourceBox.lineHeight;  // fall back to single line height
  655.  
  656.         var renderedViewportHeight = sourceBox.viewport.clientHeight;
  657.         var numberOfRenderedLines = sourceBox.numberOfRenderedLines;
  658.         if (renderedViewportHeight && numberOfRenderedLines)
  659.             averageLineHeight = renderedViewportHeight / numberOfRenderedLines;
  660.  
  661.         return averageLineHeight;
  662.     },
  663.  
  664.     /*
  665.      * The virtual sourceBox = topPadding + sourceBox.viewport + bottomPadding
  666.      * The viewport grows as more lines are added to the cache
  667.      * The virtual sourceBox height is estimated from the average height lines in the viewport cache
  668.      */
  669.     getTotalPadding: function(sourceBox)
  670.     {
  671.         var numberOfRenderedLines = sourceBox.numberOfRenderedLines;
  672.         if (!numberOfRenderedLines)
  673.             return 0;
  674.  
  675.         var max = sourceBox.maximumLineNumber;
  676.         var averageLineHeight = this.getAverageLineHeight(sourceBox);
  677.         // total box will be the average line height times total lines
  678.         var virtualSourceBoxHeight = Math.floor(max * averageLineHeight);
  679.         if (virtualSourceBoxHeight < sourceBox.clientHeight)
  680.         {
  681.             var scrollBarHeight = sourceBox.offsetHeight - sourceBox.clientHeight;
  682.             // the total - view-taken-up - scrollbar
  683.             var totalPadding = sourceBox.clientHeight - sourceBox.viewport.clientHeight - 1;
  684.         }
  685.         else
  686.             var totalPadding = virtualSourceBoxHeight - sourceBox.viewport.clientHeight;
  687.  
  688.         return totalPadding;
  689.     },
  690.  
  691.     setViewportPadding: function(sourceBox, viewRange)
  692.     {
  693.         var firstRenderedLineElement = sourceBox.getLineNode(sourceBox.firstRenderedLine);
  694.         if (!firstRenderedLineElement)
  695.         {
  696.             return;
  697.         }
  698.  
  699.         var firstRenderedLineOffset = firstRenderedLineElement.offsetTop;
  700.         var firstViewRangeElement = sourceBox.getLineNode(viewRange.firstLine);
  701.         var firstViewRangeOffset = firstViewRangeElement.offsetTop;
  702.         var topPadding = sourceBox.scrollTop - (firstViewRangeOffset - firstRenderedLineOffset);
  703.         // Because of rounding when converting from pixels to lines, topPadding can be +/- lineHeight/2, round up
  704.         var averageLineHeight = this.getAverageLineHeight(sourceBox);
  705.         var linesOfPadding = Math.floor( (topPadding + averageLineHeight)/ averageLineHeight);
  706.         var topPadding = (linesOfPadding - 1)* averageLineHeight;
  707.  
  708.         var totalPadding = this.getTotalPadding(sourceBox);
  709.         if (totalPadding < 0)
  710.             var bottomPadding = Math.abs(totalPadding);
  711.         else
  712.             var bottomPadding = Math.floor(totalPadding - topPadding);
  713.  
  714.         if (bottomPadding < 0)
  715.             bottomPadding = 0;
  716.  
  717.         var view = sourceBox.viewport;
  718.  
  719.         // Set the size on the line number field so the padding is filled with same style as source lines.
  720.         view.previousSibling.style.height = topPadding + "px";
  721.         view.nextSibling.style.height = bottomPadding + "px";
  722.  
  723.         //sourceRow
  724.         view.previousSibling.firstChild.style.height = topPadding + "px";
  725.         view.nextSibling.firstChild.style.height = bottomPadding + "px";
  726.  
  727.         //sourceLine
  728.         view.previousSibling.firstChild.firstChild.style.height = topPadding + "px";
  729.         view.nextSibling.firstChild.firstChild.style.height = bottomPadding + "px";
  730.     },
  731.  
  732.     applyDecorator: function(sourceBox)
  733.     {
  734.         if (this.context.sourceBoxDecoratorTimeout)
  735.         {
  736.             this.context.clearTimeout(this.context.sourceBoxDecoratorTimeout);
  737.             delete this.context.sourceBoxDecoratorTimeout;
  738.         }
  739.  
  740.         this.context.sourceBoxDecoratorTimeout = this.context.setTimeout(bindFixed(function delaySourceBoxDecorator()
  741.         {
  742.             try
  743.             {
  744.                 if (sourceBox.highlighter)
  745.                 {
  746.                     var sticky = sourceBox.highlighter(sourceBox);
  747.                     if (!sticky)
  748.                         delete sourceBox.highlighter;
  749.                 }
  750.                 sourceBox.decorator.decorate(sourceBox, sourceBox.repObject);
  751.  
  752.                 if (Firebug.uiListeners.length > 0) dispatch(Firebug.uiListeners, "onApplyDecorator", [sourceBox]);
  753.             }
  754.             catch (exc)
  755.             {
  756.             }
  757.         }, this));
  758.     },
  759. });
  760.  
  761.  
  762.  
  763.  
  764.     // ************************************************************************************************
  765. }});
  766.